﻿' Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind


Namespace Microsoft.CodeAnalysis.VisualBasic
    Partial Friend NotInheritable Class LocalRewriter
        Public Overrides Function VisitAsNewLocalDeclarations(node As BoundAsNewLocalDeclarations) As BoundNode
            Dim builder = ArrayBuilder(Of BoundStatement).GetInstance()

            ' The initializer expression may contain placeholder values and typically they are replaced with bound locals that
            ' get created in the local rewriter when rewriting the expression. 
            ' 
            ' There is one case where the placeholder does not get replaced by a bound temporary and needs to be replaced by
            ' the currently declared local. This is the case for an object creation expression with a object initializer if it's
            ' used in an AsNew declaration and the type is a value type, e.g.:
            ' Dim x As New ValType() With {...} 
            '     or
            ' Dim x, y As New ValType() With {...} 
            ' Because only this method knows the bound local that the placeholder will be replaced with, we need to fill 
            ' the replacement map here, before rewriting the local declaration (there is a special case, because the first 
            ' example will be bound to a BoundLocalDeclaration, see LocalRewriter.VisitLocalDeclaration for the special case).
            '
            Dim objectInitializer As BoundObjectInitializerExpression = GetBoundObjectInitializerFromInitializer(node.Initializer)

            Dim localDeclarations = node.LocalDeclarations
            For declarationIndex = 0 To localDeclarations.Length - 1
                Dim localDeclaration = localDeclarations(declarationIndex)
                Debug.Assert(localDeclaration.InitializerOpt Is Nothing)

                Dim rewrittenInitializer As BoundNode = Nothing

                Dim localSymbol = localDeclaration.LocalSymbol
                Dim staticLocalBackingFields As KeyValuePair(Of SynthesizedStaticLocalBackingField, SynthesizedStaticLocalBackingField) = Nothing

                If localSymbol.IsStatic Then
                    staticLocalBackingFields = CreateBackingFieldsForStaticLocal(localSymbol, hasInitializer:=True)
                End If

                ' rewrite the initializer for each declared variable because the initializer may contain placeholders that need
                ' to be replaced with the current bound local
                If objectInitializer IsNot Nothing Then
                    Debug.Assert(objectInitializer.PlaceholderOpt IsNot Nothing)

                    Dim initializerToRewrite As BoundExpression = node.Initializer
                    Dim placeholder As BoundWithLValueExpressionPlaceholder = objectInitializer.PlaceholderOpt

                    If declarationIndex > 0 Then

                        ' For all variables except the first one we rebind the initializer
                        ' and throw away diagnostics, those are supposed to be reported in the first
                        ' binding and don't need to be duplicated as Dev10/11 does
                        '
                        ' Note that we have to rebind the initializers because current implementation of lambda 
                        ' rewriter does not handle correctly blocks and locals which are reused in bound tree. 
                        ' Actually it seems to heavily rely on an assumption that the bound tree is a tree, not 
                        ' a DAG, with just minor deviations. Thus, the natural way of just rewriting bound 
                        ' initializer stored in node.Initializer simply does not work because rewriting *may* 
                        ' keep many blocks and locals unchanged and reuse them in all rewritten initializers, 
                        ' in which case if we have a lambda inside the initializer lambda rewriter simply throws.
                        '
                        ' Another option to satisfy lambda rewriter would be to deep-clone bound tree, but 
                        ' in this case we will also have to clone all locals and all references to locals.
                        ' We might want to look into this option later.

                        Debug.Assert(node.Syntax IsNot Nothing)
                        Debug.Assert(node.Syntax.Kind = SyntaxKind.VariableDeclarator)
                        Dim asNew = DirectCast(DirectCast(node.Syntax, VariableDeclaratorSyntax).AsClause, AsNewClauseSyntax)

                        Dim objectCreationExpressionSyntax = DirectCast(asNew.NewExpression, ObjectCreationExpressionSyntax)
                        Dim localType As TypeSymbol = localSymbol.Type

                        ' New placeholder
                        placeholder = New BoundWithLValueExpressionPlaceholder(asNew, localType)
                        placeholder.SetWasCompilerGenerated()

                        ' Rebind and discard diagnostics
                        Dim diagnostics As DiagnosticBag = DiagnosticBag.GetInstance()
                        initializerToRewrite = objectInitializer.Binder.BindObjectCreationExpression(asNew.Type,
                                                                                                     objectCreationExpressionSyntax.ArgumentList,
                                                                                                     localType,
                                                                                                     objectCreationExpressionSyntax,
                                                                                                     diagnostics,
                                                                                                     placeholder)
                        diagnostics.Free()
                    End If

                    If Not objectInitializer.CreateTemporaryLocalForInitialization Then
                        AddPlaceholderReplacement(placeholder,
                                                  VisitExpressionNode(New BoundLocal(localDeclaration.Syntax,
                                                                      localSymbol,
                                                                      localSymbol.Type)))
                    End If

                    rewrittenInitializer = Me.VisitAndGenerateObjectCloneIfNeeded(initializerToRewrite)

                    If Not objectInitializer.CreateTemporaryLocalForInitialization Then
                        RemovePlaceholderReplacement(placeholder)
                    End If

                Else
                    rewrittenInitializer = Me.VisitAndGenerateObjectCloneIfNeeded(node.Initializer)
                End If

                Dim initialization = RewriteLocalDeclarationAsInitializer(
                    localDeclaration,
                    DirectCast(rewrittenInitializer, BoundExpression),
                    staticLocalBackingFields,
                    objectInitializer Is Nothing OrElse objectInitializer.CreateTemporaryLocalForInitialization)

                builder.Add(initialization)
            Next

            Return New BoundStatementList(node.Syntax, builder.ToImmutableAndFree())
        End Function

        Private Shared Function GetBoundObjectInitializerFromInitializer(initializer As BoundExpression) As BoundObjectInitializerExpression
            If initializer IsNot Nothing AndAlso (initializer.Kind = BoundKind.ObjectCreationExpression OrElse initializer.Kind = BoundKind.NewT) Then
                Dim objectCreationExpression = DirectCast(initializer, BoundObjectCreationExpressionBase)
                Return TryCast(objectCreationExpression.InitializerOpt, BoundObjectInitializerExpression)
            End If
            Return Nothing
        End Function

    End Class
End Namespace
